﻿
Imports std

Namespace expression
    Public Module expression
        Private Enum priority
            _0 = 0
            _1
            count
        End Enum

        Private Enum oper_e
            plus = 0
            minus
            multi
            div

            count
        End Enum

        Private Class oper_t
            Public oper As oper_e
            Public pri As Int32
        End Class

        Private Const leftBracket As Char = character.leftBracket
        Private ReadOnly leftBracketLen As Int64 = strlen(leftBracket)
        Private Function readLeftBracket(ByVal exp As String, ByRef i As Int64) As Boolean
            If strsame(exp, i, leftBracket, 0, leftBracketLen) Then
                i += leftBracketLen
                Return True
            Else
                Return False
            End If
        End Function

        Private Const rightBracket As Char = character.rightBracket
        Private ReadOnly rightBracketLen As Int64 = strlen(rightBracket)
        Private Function readRightBracket(ByVal exp As String, ByRef i As Int64) As Boolean
            If strsame(exp, i, rightBracket, 0, rightBracketLen) Then
                i += rightBracketLen
                Return True
            Else
                Return False
            End If
        End Function

        Private Function charToInt(ByVal exp As String, ByVal i As Int64) As Byte
            If isdebugmode() Then
                assert(isdigit(exp(i)))
            End If
            Dim o As Byte
            assert(convertor.toint8(exp(i), o))
            Return o
        End Function

        Private Function readDouble(ByVal exp As String, ByRef i As Int64, ByRef out As Double) As Boolean
            Dim base As Int64 = 0
            base = i
            out = 0
            Dim afterDot As Int32 = 0
            Dim dec As Double = 0
            While i < strlen(exp)
                If isdigit(exp(i)) Then
                    If afterDot = 0 Then
                        out *= 10
                        out += charToInt(exp, i)
                    Else
                        dec *= 10
                        dec += charToInt(exp, i)
                        afterDot += 1
                    End If
                ElseIf exp(i) = character.decimalPoint Then
                    If afterDot = 0 Then
                        afterDot = 1
                    Else
                        Exit While
                    End If
                Else
                    Exit While
                End If
                i += 1
            End While

            If i > base Then
                out += dec * Math.Pow(10, -(afterDot - 1))
                Return True
            Else
                Return False
            End If
        End Function

        Public Sub readDoubleTest()
            Dim i As String = Nothing
            i = Console.ReadLine()
            While Not i Is Nothing
                Dim o As Double = 0
                Dim j As Int64 = 0
                If readDouble(i, j, o) Then
                    Console.WriteLine(Convert.ToString(o) + " until " + Convert.ToString(j))
                Else
                    Console.WriteLine("failed")
                End If
                i = Console.ReadLine()
            End While
        End Sub

        Private Const plus As Char = character.plusSign
        Private ReadOnly plusLen As Int64 = strlen(plus)
        Private Const minus As Char = character.minusSign
        Private ReadOnly minusLen As Int64 = strlen(minus)
        Private Const multi As Char = character.multiplicationSign
        Private ReadOnly multiLen As Int64 = strlen(multi)
        Private Const div As Char = character.divisionSign
        Private ReadOnly divLen As Int64 = strlen(div)
        Private Function readOper(ByVal exp As String, ByRef i As Int64, ByRef out As oper_t) As Boolean
            out = New oper_t()
            If strsame(exp, i, plus, 0, plusLen) Then
                out.oper = oper_e.plus
                out.pri = priority._0
                i += plusLen
                Return True
            ElseIf strsame(exp, i, minus, 0, minusLen) Then
                out.oper = oper_e.minus
                out.pri = priority._0
                i += minusLen
                Return True
            ElseIf strsame(exp, i, multi, 0, multiLen) Then
                out.oper = oper_e.multi
                out.pri = priority._1
                i += multiLen
                Return True
            ElseIf strsame(exp, i, div, 0, divLen) Then
                out.oper = oper_e.div
                out.pri = priority._1
                i += divLen
                Return True
            Else
                out = Nothing
                Return False
            End If
        End Function

        Private Sub calculateLast(ByVal oper As stack(Of oper_t), ByVal num As stack(Of Double))
            assert(oper.size() > 0)
            assert(num.size() > 1)
            Dim first As Double = 0
            Dim second As Double = 0
            Dim op As oper_t = Nothing
            second = num.back()
            num.pop()
            first = num.back()
            num.pop()
            op = oper.back()
            oper.pop()
            Select Case op.oper
                Case oper_e.plus
                    first += second
                Case oper_e.minus
                    first -= second
                Case oper_e.multi
                    first *= second
                Case oper_e.div
                    first /= second
                Case Else
                    assert(False)
            End Select
            num.push(first)
        End Sub

        'support + - * / ( ) now
        Public Function calculate(ByVal exp As String, ByRef rtn As Double) As Boolean
            Dim pri As Int32 = 0
            Dim oper As stack(Of oper_t) = Nothing
            Dim num As stack(Of Double) = Nothing
            oper = New stack(Of oper_t)()
            num = New stack(Of Double)()

            Dim i As Int64 = 0
            Dim op As oper_t = Nothing
            Dim dig As Double = 0
            While i < strlen(exp)
                If readLeftBracket(exp, i) Then
                    pri += 1
                ElseIf readRightBracket(exp, i) Then
                    pri -= 1
                    calculateLast(oper, num)
                ElseIf readOper(exp, i, op) Then
                    op.pri += pri * priority.count
                    If Not oper.empty() AndAlso op.pri <= oper.back().pri Then
                        calculateLast(oper, num)
                    End If
                    oper.push(op)
                ElseIf readDouble(exp, i, dig) Then
                    num.push(dig)
                Else
                    Return False
                End If
            End While

            If num.size() = oper.size() + 1 Then
                While num.size() > 1
                    calculateLast(oper, num)
                End While
                rtn = num.back()
                Return True
            Else
                Return False
            End If
        End Function

        Public Sub calculateTest()
            Dim i As String = Nothing
            i = Console.ReadLine()
            While Not i Is Nothing
                Dim o As Double = 0
                If calculate(i, o) Then
                    Console.WriteLine(Convert.ToString(o))
                Else
                    Console.WriteLine("failed")
                End If
                i = Console.ReadLine()
            End While
        End Sub
    End Module
End Namespace
